﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using UPNPLib;
using System.Xml;
using System.Xml.Linq;

namespace UPnPControllerServices
{
    public class UPnPControllerService : IUPnPMediaBrowserService
    {
        public const string MediaServerType = "urn:schemas-upnp-org:device:MediaServer:1";

        public const string ContentDirectoryType = "urn:schemas-upnp-org:service:ContentDirectory:1";

        public const string MediaRendererType = "urn:schemas-upnp-org:device:MediaRenderer:1";

        public const string AVTransportType = "urn:schemas-upnp-org:service:AVTransport:1";

        public ICollection<MediaServer> GetAvailableMediaServers()
        {
            UPnPDeviceFinder deviceFinder = new UPnPDeviceFinder();

            IUPnPDevices devices = deviceFinder.FindByType(UPnPControllerService.MediaServerType, 0);

            List<MediaServer> servers = new List<MediaServer>();

            MediaServer currentServer;
            foreach (UPnPDevice device in devices)
            {
                currentServer = new MediaServer()
                {
                    FriendlyName = device.FriendlyName,
                    ServerIdentifier = new Guid(device.UniqueDeviceName.Substring(device.UniqueDeviceName.IndexOf(":") + 1)),
                    IconUrl = device.IconURL("image/png", 48, 48, 24)
                };

                servers.Add(currentServer);
            }

            return servers;
        }

        public ICollection<MediaItem> GetAvailableMediaFromServer(Guid serverId)
        {
            List<MediaItem> results = new List<MediaItem>();

            UPnPService directory = FindContentDirectoryForUDN(string.Format("uuid:{0}", serverId));

            if (directory != null)
            {
                var allVideos = GetAllVideos(directory);

                MediaItem item;
                foreach (var video in allVideos)
                {
                    var uriCandidates = LoadUriCandidates(directory, video.Parent.Attribute("id").Value);

                    if (uriCandidates != null)
                    {
                        item = CreateMediaItemFromMetaData(serverId, video, uriCandidates);

                        results.Add(item);
                    }
                }
            }

            return results;
        }

        private UPnPService FindContentDirectoryForUDN(string udn)
        {
            UPnPDevice device = FindDeviceByUDN(udn);

            if (device != null)
            {
                return FindContentDirectory(device);
            }

            return null;
        }

        private UPnPDevice FindDeviceByUDN(string udn)
        {
            UPnPDeviceFinder deviceFinder = new UPnPDeviceFinder();

            return deviceFinder.FindByUDN(udn);
        }

        private MediaItem CreateMediaItemFromMetaData(Guid serverId, XElement video, IOrderedEnumerable<XElement> uriCandidates)
        {
            return new MediaItem()
            {
                MediaItemId = video.Parent.Attribute("id").Value,
                Name = video.Value,
                ServerId = serverId,
                UriLocation = uriCandidates.FirstOrDefault().Value
            };
        }

        private IEnumerable<XElement> GetAllVideos(UPnPService directory)
        {
            XDocument doc;
            int id = FindFirstMatchingContainerId(directory, 0, "Videos");

            if (id != -1)
            {
                id = FindFirstMatchingContainerId(directory, id, "All Videos");

                doc = LoadXmlDataForId(id, directory);

                var allVideos =
                        (from x in doc.Descendants()
                         where x.Name.LocalName == "title"
                         select x);

                return allVideos;
            }

            return null;
        }

        private IOrderedEnumerable<XElement> LoadUriCandidates(UPnPService directory, string id)
        {
            XDocument doc = LoadXmlDataForItem(id, directory);

            if (doc != null)
            {
                var uriCandidates = from res
                                    in doc.Descendants()
                                    where !res.HasElements && res.Attribute("bitrate") != null
                                        && (res.Value.Contains('?') ? res.Value.Substring(0, res.Value.LastIndexOf('?')) : res.Value).EndsWith(".wmv")
                                    orderby decimal.Parse(res.Attribute("bitrate").Value) descending
                                    select res;
             
                return uriCandidates;
            }

            return null;
        }

        private int FindFirstMatchingContainerId(UPnPService directory, int startId, string containerName)
        {
            XDocument doc = LoadXmlDataForId(startId, directory);

            if (doc != null)
            {
                var videos = from container
                             in doc.Descendants()
                             where container.Value == containerName
                             select container;

                var videoContainer = videos.FirstOrDefault();

                if (videoContainer != null)
                {
                    string idValue = videoContainer.Parent.Attribute("id").Value;
                    int id = int.Parse(idValue);

                    return id;
                }
            }

            return -1;
        }

        private XDocument LoadXmlDataForItem(string id, UPnPService directory)
        {
            object output = new object();

            object[] input = new object[6]
                    {
                        id, // Id
                        "BrowseMetadata",
                        "upnp:album,upnp:artist,upnp:genre,upnp:title,res@size,res@duration,res@bitrate,res@sampleFrequency,res@bitsPerSample,res@nrAudioChannels,res@protocolInfo,res@protection,res@importUri", // filter
                        0, // start index
                        1, // max result count
                        "" // sort criteria
                    };

            directory.InvokeAction("Browse", input, ref output);

            object[] allResults = output as object[];
            if (allResults != null)
            {
                XDocument doc = XDocument.Parse(allResults[0].ToString());

                return doc;
            }

            return null;
        }

        private XDocument LoadXmlDataForId(int id, UPnPService directory)
        {
            object output = new object();

            object[] input = new object[6]
                    {
                        id, // Id
                        "BrowseDirectChildren",
                        "", // filter
                        0, // start index
                        0, // max result count
                        "" // sort criteria
                    };

            try
            {
                directory.InvokeAction("Browse", input, ref output);

                object[] allResults = output as object[];
                if (allResults != null)
                {
                    XDocument doc = XDocument.Parse(allResults[0].ToString());

                    return doc;
                }
            }
            catch (Exception ex)
            {

            }

            return null;
        }

        private UPnPService FindContentDirectory(UPnPDevice device)
        {
            try
            {
                foreach (UPnPService service in device.Services)
                {
                    if (service.ServiceTypeIdentifier == UPnPControllerService.ContentDirectoryType)
                    {
                        return service;
                    }
                }
            }
            catch
            {
            }

            return null;
        }

        public ICollection<MediaRenderer> GetAvailableMediaRenderers()
        {
            UPnPDeviceFinder deviceFinder = new UPnPDeviceFinder();

            IUPnPDevices devices = deviceFinder.FindByType(UPnPControllerService.MediaRendererType, 0);

            List<MediaRenderer> servers = new List<MediaRenderer>();

            MediaRenderer renderer;
            foreach (UPnPDevice device in devices)
            {
                renderer = new MediaRenderer()
                {
                    Description = device.Description,
                    FriendlyName = device.FriendlyName,
                    RendererId = new Guid(device.UniqueDeviceName.Substring(device.UniqueDeviceName.IndexOf(":") + 1))
                };

                servers.Add(renderer);
            }

            return servers;
        }

        public void PlayMedia(string mediaItemUri, Guid serverId, Guid rendererId)
        {
            UPnPService service = GetAVTransportServiceForUDN(rendererId);

            Stop(service);

            SetMediaUri(service, mediaItemUri);

            Play(service);
        }

        private void Play(UPnPService service)
        {
            object[]  inparams = new object[2]
                    {
                        0, // instance ID
                        "1" // transport play speed
                    };

            object outParams = new object();

            service.InvokeAction("Play", inparams, ref outParams);
        }

        private void SetMediaUri(UPnPService service, string mediaUri)
        {
            object[] inparams = new object[3]
                    {
                        0, // instance ID
                        mediaUri, // Uri for media
                        "" // current URI metadata
                    };

            object outParams = new object();
            service.InvokeAction("SetAVTransportURI", inparams, ref outParams);

            System.Threading.Thread.Sleep(500);
        }

        private void Stop(UPnPService service)
        {
            object[] inparams = new object[1]
                    {
                        0 // instance ID
                    };

            object outParams = new object();
            service.InvokeAction("Stop", inparams, ref outParams);

            System.Threading.Thread.Sleep(500);
        }

        private void Pause(UPnPService service)
        {
            object[] inparams = new object[1]
                    {
                        0 // instance ID
                    };

            object outParams = new object();
            service.InvokeAction("Pause", inparams, ref outParams);
        }

        public void Stop(Guid rendererId)
        {
            UPnPService service = GetAVTransportServiceForUDN(rendererId);
            Stop(service);
        }

        public void Pause(Guid rendererId)
        {
            UPnPService service = GetAVTransportServiceForUDN(rendererId);
            Pause(service);
        }

        public void SkipForward(Guid rendererId, int skipSeconds)
        {
            throw new NotImplementedException();
        }

        public void SkipBackwards(Guid rendererId, int skipSeconds)
        {
            throw new NotImplementedException();
        }

        public void ResumePlay(Guid rendererId)
        {
            UPnPService service = GetAVTransportServiceForUDN(rendererId);
            Play(service);
        }

        private UPnPService GetAVTransportServiceForUDN(Guid deviceId)
        {
            UPnPDeviceFinder deviceFinder = new UPnPDeviceFinder();
            UPnPDevice renderer = deviceFinder.FindByUDN(string.Format("uuid:{0}", deviceId));

            foreach (UPnPService service in renderer.Services)
            {
                if (service.ServiceTypeIdentifier == UPnPControllerService.AVTransportType)
                {
                    return service;
                }
            }

            return null;
        }
    }
}